#include "IndexSet.h"
#include "File.h"
#include "ImposibleWriteFileException.h"
#include "ImposibleReadFileException.h"
#include "ImposibleOpenFileException.h"
#include "ImposibleCloseFileException.h"
#include "NotExistNameException.h"
#include <algorithm>

#define MAX_SIZE 4096


#ifndef ZERO
	#define ZERO 0
#endif

#ifndef UNO
	#define UNO 1
#endif

#ifndef ROOT_OFFSET
	#define ROOT_OFFSET 128
#endif

int maxSizeIndexSet = MAX_SIZE;

IndexSet::IndexSet()
	: filename(""),
	offset(ZERO),
	level(ZERO)
{
}

IndexSet::IndexSet(const IndexSet& toCopy)
{
	filename = toCopy.filename;
	offset = toCopy.offset;
	level = toCopy.level;
	keys = toCopy.keys;
	hijos = toCopy.hijos;
}

IndexSet::~IndexSet()
{
	filename.clear();
	keys.clear();
	hijos.clear();
}

void IndexSet::setFilename(std::string name)
{
	filename = name;
}

void IndexSet::getFilename(std::string &name)
{
	name = filename;
}

void IndexSet::setOffset(int cur)
{
	offset = cur;
}

int IndexSet::getOffset()
{
	return offset;
}

void IndexSet::setLevel(unsigned int nivel)
{
	level = nivel;
}

unsigned int IndexSet::getLevel()
{
	return level;
}

int IndexSet::add(Key *key)
{
	int retorno = I_ERROR;
	int pos = ZERO;
	unsigned int size = ZERO;
	bool flag = false;
	std::vector<Key>::iterator current = keys.begin();

	if(key != NULL)
	{
		size = this->computeSize();

		if( ((double)(size + key->getSize() + sizeof(int))) > ((double)(maxSizeIndexSet * 0.9)) )
			return I_OVERFLOW;

		while(current != keys.end() && !flag)
		{
			if((*key) < (*current))
			{
				keys.insert(current,(*key));
				flag = true;
				retorno = pos;
			}
			else
				pos++;
			current++;
			
			
		}
		if(!flag)
		{
			keys.push_back((*key));
			retorno = pos;
		}
		
	}
	
	return retorno;
}

int IndexSet::update(Key *key)
{
	int retorno = I_ERROR;
	bool flag = false;
	std::vector<Key>::iterator current = keys.begin();

	if(key != NULL)
	{
		while(current != keys.end() && !flag)
		{
			if((*key) == (*current))
			{
				keys.erase(current);
				keys.insert(current, (*key));
				flag = true;
			}
			current++;
		}
		if(flag)
			retorno = I_OK;
		else 
			retorno = I_NO_EXISTE;
	}
	return retorno;
}

int IndexSet::remove(Key *key)
{
	int retorno = I_ERROR;
	bool flag = false;
	int pos = ZERO;
	unsigned int size = ZERO;
	std::vector<Key>::iterator current = keys.begin();

	if(key != NULL)
	{
		size = this->computeSize();

		if( ((double)(size - key->getSize() - sizeof(int))) < ((double)(maxSizeIndexSet * 0.5)) )
			return I_UNDERFLOW;

		while(current != keys.end() && !flag)
		{
			if((*key) == (*current) && key->getOffset() == (*current).getOffset())
			{
				keys.erase(current);
				flag = true;
				retorno = pos;
			}
			else
				pos++;
			current++;
		}
		if(!flag)
			retorno = I_NO_EXISTE;
	}
	return retorno;
}

int IndexSet::get(Key *key)
{
	int retorno = I_ERROR;
	bool flag = false;
	std::vector<Key>::iterator current = keys.begin();

	if(key != NULL)
	{
		while(current != keys.end() && !flag)
		{
			if((*key) == (*current))
			{
				key->setOffset((*current).getOffset());
				flag = true;
			}
			current++;
		}
		if(flag)
			retorno = I_OK;
		else 
			retorno = I_NO_EXISTE;
	}
	return retorno;
}

int IndexSet::get(unsigned int pos, Key *key)
{
	int retorno = I_POSICION_INVALIDA;
	std::vector<Key>::iterator current = keys.begin();

	if(key != NULL && pos < keys.size())
	{
		current += pos;
		(*key) = (*current);
		retorno = I_OK;
	}
	return retorno;
}

bool IndexSet::findLastKey(Key* key, unsigned int &pos)
{
	std::vector<Key>::iterator itCursor;
	bool find = false;

	if(key != NULL && keys.size() > ZERO)
	{
		itCursor = keys.begin();

		if((*key) < (*itCursor))
		{
			pos = ZERO;
			return false;
		}
		else
		{
			pos = ZERO;
			while((itCursor != keys.end()) && (!find))
			{
				if( (*key) < (*itCursor))
					find = true;
				else
				{
					itCursor++;
					pos++;
				}
			}
			if(find)
				return true;
		}

	}
	return false;
}

bool IndexSet::findFirstKey(Key* key, unsigned int &pos)
{
	std::vector<Key>::iterator itCursor;
	bool find = false;

	if(key != NULL && keys.size() > ZERO)
	{
		itCursor = keys.begin();

		if((*key) < (*itCursor))
		{
			pos = ZERO;
			return false;
		}
		else
		{
			pos = ZERO;
			while((itCursor != keys.end()) && (!find))
			{
				if( (*key) < (*itCursor) || (*key) == (*itCursor))
					find = true;
				else
				{
					itCursor++;
					pos++;
				}
			}
			if(find)
				return true;
		}

	}
	return false;
}

int IndexSet::addNrr(unsigned int pos, int son)
{	
	std::vector<int>::iterator current;

	if(pos >= ZERO && pos <= hijos.size())
	{
		current = hijos.begin() + pos;
		hijos.insert(current, son);
		return I_OK;
	}
	return I_POSICION_INVALIDA;
}

int IndexSet::removeNrr(unsigned int pos)
{
	std::vector<int>::iterator current;

	if(pos >= ZERO && pos <= hijos.size())
	{
		current = hijos.begin() + pos;
		hijos.erase(current);
		return I_OK;
	}
	return I_POSICION_INVALIDA;
}

int IndexSet::getNrr(unsigned int pos, int &son)
{
	std::vector<int>::iterator current;

	if(pos >= ZERO && pos <= hijos.size())
	{
		current = hijos.begin() + pos;
		son = (*current);
		return I_OK;
	}
	return I_POSICION_INVALIDA;
}

unsigned int IndexSet::getCount()
{
	return keys.size();
}

unsigned int IndexSet::computeSize()
{
	unsigned int retorno = sizeof(int) + sizeof(unsigned int);
	std::vector<Key>::iterator current = keys.begin();

	while(current != keys.end())
	{
		char ptr[MAX_SIZE] = {ZERO};
		unsigned int sizeKey = ZERO;

		(*current).getKey(ptr, sizeKey);
		retorno += (sizeof(unsigned int) + sizeKey + sizeof(long int));
		current++;
	}
	retorno += (keys.size() * sizeof(int));

	return retorno;
}

void IndexSet::load()
{
	File *file = NULL;
	char record[MAX_SIZE] = {ZERO};
	char *current = record;
	unsigned int countKey = ZERO;
	unsigned int i = ZERO;

	try
	{
		if(filename == "")
		{	
			throw NotExistNameException ();
		}
		file = new File();

		if((file->open(filename.c_str()))==I_ERROR)
		{	
			if(file != NULL)
				delete file;
			throw ImposibleOpenFileException ();
		}
	
		file->setCurrentPos(offset);
		if((file->read(record, MAX_SIZE))==I_ERROR )
		{	
			if(file != NULL)
				delete file;
			throw ImposibleReadFileException ();
		}
		
		if(file->close() == I_ERROR)
		{
			if(file != NULL)
				delete file;
			throw ImposibleCloseFileException ();
		}
	
		memcpy((char*)&level, current, sizeof(int));
		current += sizeof(int);
		memcpy((char*)&countKey, current, sizeof(unsigned int));
		current += sizeof(unsigned int);
		
	
		for(i = ZERO; i < countKey; i++)
		{
			char ptr[MAX_SIZE] = {ZERO};
			unsigned int sizeKey = ZERO;
			int pos = ZERO;
			Key key;
		
			memcpy((char*)&sizeKey, current, sizeof(unsigned int));
			current += sizeof(unsigned int);	
			memcpy(ptr, current, sizeKey);
			current += sizeKey;
			memcpy((char*)&pos, current, sizeof(int));
			current += sizeof(int);
	
			key.setKey(ptr, sizeKey, pos);
	
			keys.push_back(key);
		}
	
		for(i = ZERO; i <= countKey; i++)
		{
			int hijo = ZERO;
			memcpy((char*)&hijo, current, sizeof(int));
			current += sizeof(int);
	
			hijos.push_back(hijo);
		}
		
		delete file;
	}
	catch ( NotExistNameException & ex1)
	{
		throw NotExistNameException ();
	}
	catch ( ImposibleOpenFileException & ex2)
	{
		throw ImposibleOpenFileException ();
	}
	catch ( ImposibleCloseFileException  & ex3)
	{
		throw ImposibleCloseFileException ();
	}
	catch ( ImposibleReadFileException  & ex3)
	{
		throw ImposibleReadFileException ();
	}
	catch ( EjerciceException & ex4)
	{
		throw EjerciceException ();
	}
}

void IndexSet::save()
{
	File *file = NULL;
	char record[MAX_SIZE] = {ZERO};
	char *current = record;
	unsigned int countKey = keys.size();
	
	try
	{
		if(filename == "")
		{	
			throw NotExistNameException ();
		}
		file = new File();

		if((file->open(filename.c_str()))==I_ERROR)
		{	
			if(file != NULL)
				delete file;
			throw ImposibleOpenFileException ();
		}
	
		file->setCurrentPos(offset);

		memcpy(current, &level, sizeof(int));
		current += sizeof(int);
	
		memcpy(current, &countKey, sizeof(unsigned int));
		current += sizeof(unsigned int);
	
		std::vector<Key>::iterator currentKey = keys.begin();
	
		while(currentKey != keys.end())
		{
			char ptr[MAX_SIZE] = {ZERO};
			unsigned int sizeKey = ZERO;
			int pos = ZERO;
	
			(*currentKey).getKey(ptr, sizeKey);
			pos = (*currentKey).getOffset();
			
			memcpy(current, &sizeKey, sizeof(unsigned int));
			current += sizeof(unsigned int);
			memcpy(current, ptr, sizeKey);
			current += sizeKey;
			memcpy(current, &pos, sizeof(int));
			current += sizeof(int);
	
			currentKey++;
		}
	
		std::vector<int>::iterator currentHijos = hijos.begin();
	
		while(currentHijos != hijos.end())
		{
			int hijo = (*currentHijos);
			memcpy(current, (char*)&hijo, sizeof(int));
			current += sizeof(int);
			currentHijos++;
		}

		if((file->write(record, MAX_SIZE))==I_ERROR )
		{	
			if(file != NULL)
				delete file;
			throw ImposibleWriteFileException ();
		}
		
		if(file->close() == I_ERROR)
		{
			if(file != NULL)
				delete file;
			throw ImposibleCloseFileException ();
		}	
		
		delete file;
	}
	catch ( NotExistNameException & ex1)
	{
		throw NotExistNameException ();
	}
	catch ( ImposibleOpenFileException & ex2)
	{
		throw ImposibleOpenFileException ();
	}
	catch ( ImposibleCloseFileException  & ex3)
	{
		throw ImposibleCloseFileException ();
	}
	catch ( ImposibleWriteFileException  & ex3)
	{
		throw ImposibleWriteFileException ();
	}
	catch ( EjerciceException & ex4)
	{
		throw EjerciceException ();
	}
}

void IndexSet::split(Key *newKey, int newNrr, Key* promo, int &nrrPromo)
{
	std::vector<Key> :: iterator it;
	std::vector<int> :: iterator itt;
	unsigned int cantidad = ZERO;
	unsigned int contador = ZERO;
	int pos = ZERO;
	IndexSet* newPage = NULL;
	File* file = NULL;

	try
	{

		if(newKey != NULL)
		{
			// obtengo la cantidad de claves a mover
			cantidad = keys.size() / 2;
		
			// creo la pagina nueva
			newPage = new IndexSet();
			newPage->setFilename(filename);
			newPage->setLevel(level);
			
			// modifico provisoriamente el freespace de la pagina para que entre la nueva 
			maxSizeIndexSet = MAX_SIZE * 2;
			
			
			pos = this->add(newKey);
			if(pos < ZERO)
				std::cout << "SPLIT ERROR: error en la alta de la nueva clave" << std::endl;
			this->addNrr(pos + UNO, newNrr);
			
			// paso las clave a la nueva pagina
			contador = cantidad + UNO;
			it = keys.begin() + contador;
			itt = hijos.begin() + contador;
			while(contador < keys.size())
			{
				// inserto la llave y el nrr en la posicion que le corresponde
				Key key = (*it);
				pos = newPage->add(&key);
				if(pos < ZERO)
					std::cout << "SPLIT ERROR: error en la alta de la nueva clave" << std::endl;
				newPage->addNrr(pos, (*itt));
	
				it++;
				itt++;
				contador++;
			}
			newPage->addNrr(contador - cantidad - UNO, (*itt));
			
			// ahora tomo la clave y el nrr a promocionar
			if(I_OK != this->get(cantidad, promo))
				std::cout << "SPLIT ERROR: error en la obtencion del separador"  << std::endl;
	
			
			// tengo que borrar las claves en la pagina vieja 
			keys.erase(keys.begin() + cantidad, keys.end());
			hijos.erase(hijos.begin() + cantidad + UNO, hijos.end());
			
			// Reestablezco el maximo tamaño de pagina.
			maxSizeIndexSet = MAX_SIZE;
	
			this->save();
	
			if(filename == "")
			{	
				throw NotExistNameException ();
			}
			file = new File();
	
			if((file->open(filename.c_str()))==I_ERROR)
			{	
				if(file != NULL)
					delete file;
				if(newPage != NULL)
					delete newPage;
				throw ImposibleOpenFileException ();
			}
	
			nrrPromo = file->getSize((char*)filename.c_str());

			if(file->close() == I_ERROR)
			{
				if(file != NULL)
					delete file;
				if(newPage != NULL)
					delete newPage;
				throw ImposibleCloseFileException ();
			}
			
			newPage->setOffset(nrrPromo);
			delete file;
	
			newPage->save();
			
	
			delete newPage;
		}
	}
	catch ( NotExistNameException & ex1)
	{
		throw NotExistNameException ();
	}
	catch ( ImposibleOpenFileException & ex2)
	{
		throw ImposibleOpenFileException ();
	}
	catch ( ImposibleCloseFileException  & ex3)
	{
		throw ImposibleCloseFileException ();
	}
	catch ( EjerciceException & ex4)
	{
		throw EjerciceException ();
	}
}

void IndexSet::print()
{
	std::cout << "Filename: " << filename << std::endl;
	std::cout << "Offset: " << offset << std::endl;
	std::cout << "Level: " << level << std::endl;
	std::cout << "Cantidad de Claves: " << keys.size() << std::endl;

	std::vector<Key>::iterator current = keys.begin();
	std::vector<int>::iterator currentHijos = hijos.begin();
	unsigned int pos = ZERO, i = ZERO;

	while(current != keys.end())
	{
		std::cout << i << ")  ";
		(*current).print();
		current++;
		i++;
	}

	while(currentHijos != hijos.end())
	{
		std::cout << "Hijo " << pos << " : " << (*currentHijos) << std::endl;
		currentHijos++;
		pos++;
	}
}

const IndexSet &IndexSet::operator=(const IndexSet &toAssign)
{
	if(this != &toAssign)
	{
		filename = toAssign.filename;
		offset = toAssign.offset;
		level = toAssign.level;
		keys = toAssign.keys;
		hijos = toAssign.hijos;
	}
	return *this;
}



